#include <gtest/gtest.h>

#include "MyIterator.h"
#include <helper/unittest/Tracer.h>

using namespace helper::iter;

TEST(IteratorTest, testAdvanceBy){
  { //low bound
    MyIterator<int> iter(0, 10);
    ASSERT_TRUE(iter.advanceBy(0));
    ASSERT_EQ(0, iter.next().value());
  }
  { //normal 
    MyIterator<int> iter(0, 10);
    ASSERT_TRUE(iter.advanceBy(5));
    ASSERT_EQ(5, iter.next().value());
  }
  { //high bound
    MyIterator<int> iter(0, 10);
    ASSERT_TRUE(iter.advanceBy(10));
    ASSERT_FALSE(iter.next());
  }
  { //high bound + 1
    MyIterator<int> iter(0, 10);
    ASSERT_FALSE(iter.advanceBy(11));
    ASSERT_FALSE(iter.next());
  }
  { //overflow 
    MyIterator<int> iter(0, 10);
    ASSERT_FALSE(iter.advanceBy(100));
    ASSERT_FALSE(iter.next());
  }
}


TEST(IteratorTest, testNext){
  EXPECT.reset()
    .operation("iterator.next v:0")
    .operation("iterator.next v:1")
    .operation("iterator.next v:2")
    .operation("iterator.next v:3")
    .operation("iterator.next v:4")
    ;
  TRACER.reset();

  MyIterator<int> iter(0, 5);
  for(auto v = iter.next(); v; v = iter.next()){
    TRACER.operation("iterator.next v:" + std::to_string(v.value()));
  }
  ASSERT_EQ(EXPECT, TRACER);
}

TEST(IteratorTest, testForeach){
  EXPECT.reset()
    .operation("iterator.foreach v:0")
    .operation("iterator.foreach v:1")
    .operation("iterator.foreach v:2")
    .operation("iterator.foreach v:3")
    .operation("iterator.foreach v:4")
    ;
  TRACER.reset();

  MyIterator<int>(0, 5)
  .foreach([](const int & v){
    TRACER.operation("iterator.foreach v:" + std::to_string(v));
  });
  ASSERT_EQ(EXPECT, TRACER);
}

TEST(IteratorTest, testFold){

  ASSERT_EQ(std::string("01234"),
    MyIterator<int>()
    .fold<std::string>(""
      ,[](const auto & accum, const auto & v) {
        return accum + std::to_string(v);
      })
  );

  ASSERT_EQ((size_t)5, 
    MyIterator<int>()
    .fold<size_t>(0
      , [](const auto & accum, const auto & ){
        return accum + 1;
      })
  );
}
TEST(IteratorTest, testCount){
  ASSERT_EQ((size_t)5, MyIterator<int>().count());
}

TEST(IteratorTest, testAll){
	ASSERT_TRUE(MyIterator<int>(0, 0).all([](const int &v){ return v >= 0;}));
	ASSERT_FALSE(MyIterator<int>(0, 0).any([](const int &v){ return v >= 0;}));


	ASSERT_TRUE(MyIterator<int>().all([](const int &v){ return v >= 0;}));
	ASSERT_FALSE(MyIterator<int>().all([](const int &v){ return v >= 2;}));

	ASSERT_TRUE(MyIterator<int>().any([](const int &v) { return v >= 4;}));
	ASSERT_FALSE(MyIterator<int>().any([](const int &v) { return v >= 100;}));
}

TEST(IteratorTest, testFind){
	ASSERT_EQ(std::make_optional<int>(2)
    , MyIterator<int>().find([](const int & v){ return v == 2;}));

	ASSERT_EQ(std::nullopt
    , MyIterator<int>().find([](const int & v){ return v == 100;}));
}
TEST(IteratorTest, testFindMap){
	//find_map => find first, if true -> map it to new Type
	ASSERT_EQ(std::make_optional<std::string>("2"),
	  MyIterator<int>()
    .findMap<std::string>([](const int & v) -> std::optional<std::string>{ 
        if(v == 2){
          return std::make_optional<std::string>(std::to_string(v));
        }
        return std::nullopt;
    })
  );
}

TEST(IteratorTest, testNth){
  //empty [0,0)
  ASSERT_EQ(std::nullopt, MyIterator<int>(0, 0).nth(0));
  
  // [0, 1)
  ASSERT_EQ(0, MyIterator<int>(0, 1).nth(0).value());
  ASSERT_EQ(std::nullopt, MyIterator<int>(0, 1).nth(1));

  // [0, 1, 2, 3, 4, 5)
  ASSERT_EQ(0, MyIterator<int>(0, 5).nth(0).value());
  ASSERT_EQ(1, MyIterator<int>().nth(1).value());
  ASSERT_EQ(2, MyIterator<int>().nth(2).value());
  ASSERT_EQ(3, MyIterator<int>().nth(3).value());
  ASSERT_EQ(4, MyIterator<int>().nth(4).value());
  ASSERT_EQ(std::nullopt, MyIterator<int>().nth(5));
  ASSERT_FALSE(MyIterator<int>().nth(100));
  ASSERT_TRUE(MyIterator<int>().nth(0));

  // bool() ===only valid for=== > 
  //  1. if(obj) -> OK ; 
  //  2. if(!obj) -> OK
  //ERROR: ASSERT_EQ(false, MyIterator<int>().nth(100));
  //ERROR: ASSERT_EQ(true, MyIterator<int>().nth(0));
}
